//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
// Projet: MQ2VoiceCommand.cpp
// Author: A_Enchanter_00
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
#undef CINTERFACE

#include "../MQ2Plugin.h"

#include <sapi.h>
#include <initguid.h>
#include <speech.h>
#include <vector>

PreSetup("MQ2VoiceCommand");
PLUGIN_VERSION(7.0817);
//Voice Command
bool bVoiceCommandStatus = true;
bool bVoiceCommandStatusZone = false;
HRESULT				VOICEhr;
HWND				hWnd;
PIVOICECMD			gpIVoiceCommand = NULL;
PIVCMDDIALOGS		gpIVCmdDialogs = NULL;
PIVCMDMENU			gpIVCmdMenu = NULL;
PIVCMDATTRIBUTES	pIVCmdAttributes;

// TTS
ISpVoice * pVoice = NULL;
HRESULT				TTShr;
bool bSpeakTells = true;
bool bSpeakGroup = false;
bool bSpeakGuild = false;
bool bSpeakRaid = false;
bool bSpeakTellFilter = false;
bool bSpeakGroupFilter = false;
bool bSpeakGuildFilter = false;
bool bSpeakRaidFilter = false;

bool bLoaded = false;

unsigned int iSpeakVolume = 100;
int iSpeakSpeed = 0;

char matchTellTriggers[MAX_STRING];
char ignoreTellTriggers[MAX_STRING];
char matchGroupTriggers[MAX_STRING];
char ignoreGroupTriggers[MAX_STRING];
char matchGuildTriggers[MAX_STRING];
char ignoreGuildTriggers[MAX_STRING];
char matchRaidTriggers[MAX_STRING];
char ignoreRaidTriggers[MAX_STRING];

char ignoreChars[MAX_STRING];

char Buffer[MAX_STRING] = {0};					// Buffer for String manipulatsion
char CharacterEvents[MAX_STRING];
char CharacterSettings[MAX_STRING];

struct Commands {
	char Command[MAX_STRING];
	char SpokenCommand[MAX_STRING];
} pCommands;

vector<Commands> VoiceCommands;

VOID Execute(PCHAR zFormat, ...) {
	char zOutput[MAX_STRING]={0};
	va_list vaList;
	va_start(vaList,zFormat);
	vsprintf(zOutput,zFormat,vaList);
	if(!zOutput[0]) return;
	DoCommand(GetCharInfo()->pSpawn,zOutput);
}

void BeginOLE();
void BeginVoiceOLE();
void BeginTTSOLE();
void EndOLE();
BOOL GetCommands(PSDATA pData, DWORD *pdwNumCommands);
void LoadCostumEvents();
HRESULT UseCommands();
BOOL InGames();
VOID VoiceCommand(PSPAWNINFO pChar, PCHAR szLine);
PLUGIN_API VOID OnTextToSpeech(PCHAR szLine);
VOID SpeakNow(PSPAWNINFO pChar,PCHAR szLine);
bool CheckChar(char szLine);
bool FounedPlayer(PCHAR Player, PCHAR Filter);

// Voice Command notifications
class CIVCmdNotifySink : public IVCmdNotifySink {
    private:
    DWORD   m_dwRefCnt;

    public:
    CIVCmdNotifySink(void);
    ~CIVCmdNotifySink(void);

    // IUnkown members that delegate to m_punkOuter
    // Non-delegating object IUnknown
    STDMETHODIMP         QueryInterface (REFIID, LPVOID FAR *);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IVCmdNotifySink members
    STDMETHODIMP CommandRecognize (DWORD, PVCMDNAME, DWORD, DWORD, PVOID, DWORD, PSTR, PSTR);
    STDMETHODIMP CommandOther     (PVCMDNAME, PTSTR);
	STDMETHODIMP CommandStart     (void);
    STDMETHODIMP MenuActivate     (PVCMDNAME, BOOL);
    STDMETHODIMP UtteranceBegin   (void);
    STDMETHODIMP UtteranceEnd     (void);
    STDMETHODIMP VUMeter          (WORD);
    STDMETHODIMP AttribChanged    (DWORD);
	STDMETHODIMP Interference     (DWORD);
};
typedef CIVCmdNotifySink * PCIVCmdNotifySink;

void BeginOLE() {
	if(!bLoaded && InGames() ) {
		CoInitialize(NULL);

		sprintf(CharacterEvents,"%s.%s",EQADDR_SERVERNAME,GetCharInfo()->pSpawn->Name);
		sprintf(CharacterSettings,"Settings.%s.%s",EQADDR_SERVERNAME,GetCharInfo()->pSpawn->Name);
		LoadCostumEvents();
		// Voice Command
		bVoiceCommandStatus = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"VoiceCommandStatus",   1,INIFileName);
		// TTS
		bSpeakTells = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakTells",   1,INIFileName);
		bSpeakGroup = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakGroup",   1,INIFileName);
		bSpeakGuild = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakGuild",   0,INIFileName);
		bSpeakRaid = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakRaid",   0,INIFileName);

		bSpeakTellFilter = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakTellFilter",   0,INIFileName);
		bSpeakGroupFilter = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakGroupFilter",   0,INIFileName);
		bSpeakGuildFilter = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakGuildFilter",   0,INIFileName);
		bSpeakRaidFilter = 0x00000001 & GetPrivateProfileInt(CharacterSettings,"SpeakRaidFilter",   0,INIFileName);

		iSpeakVolume = GetPrivateProfileInt(CharacterSettings,"SpeakVolume",   100,INIFileName);
		iSpeakSpeed = GetPrivateProfileInt(CharacterSettings,"SpeakSpeed ",   0,INIFileName);

		GetPrivateProfileString(CharacterSettings,"matchTellTriggers","|",matchTellTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"ignoreTellTriggers","|",ignoreTellTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"matchGroupTriggers","|",matchGroupTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"ignoreGroupTriggers","|",ignoreGroupTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"matchGuildTriggers","|",matchGuildTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"ignoreGuildTriggers","|",ignoreGuildTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"matchRaidTriggers","|",matchRaidTriggers,MAX_STRING,INIFileName);
		GetPrivateProfileString(CharacterSettings,"ignoreRaidTriggers","|",ignoreRaidTriggers,MAX_STRING,INIFileName);

		GetPrivateProfileString(CharacterSettings,"ignoreChars","!|+-~^=*:;+#\\/\"<>_.(){}[]",ignoreChars,MAX_STRING,INIFileName);

		BeginVoiceOLE();
		bLoaded = true;
		BeginTTSOLE();
		WriteChatf("MQ2VoiceCommand Status Voice(%s) TTS(%s)",(bVoiceCommandStatus && SUCCEEDED(VOICEhr) && gpIVoiceCommand)?"\agon\ax":"\aroff\ax",(SUCCEEDED( TTShr ) && pVoice)?"\agon\ax":"\aroff\ax");
	}
}

void BeginVoiceOLE() {
	if( !VoiceCommands.size() ) return;
	DebugSpewAlways("MQ2VoiceCommand::BeginVoiceOLE()");
	VCMDNAME VcmdName;
	LANGUAGE Language;
	PCIVCmdNotifySink gpVCmdNotifySink = NULL;
	SetMessageQueue(96);

	// Create the voice commands object
	VOICEhr = CoCreateInstance(CLSID_VCmd, NULL, CLSCTX_LOCAL_SERVER, IID_IVoiceCmd, (LPVOID *)&gpIVoiceCommand);	

	if(FAILED(VOICEhr)) {
		WriteChatf("MQ2VoiceCommand::BeginVoiceOLE()::\arError\ax in registering voice command section");
		return;
	}
	// Get the dialogs interface pointer...
	VOICEhr = gpIVoiceCommand->QueryInterface( IID_IVCmdDialogs, (LPVOID FAR *)&gpIVCmdDialogs );	

	// Create/Register VCmd notification sink...
	gpVCmdNotifySink = new CIVCmdNotifySink;

	VOICEhr = gpIVoiceCommand->Register( "", gpVCmdNotifySink, IID_IVCmdNotifySink, VCMDRF_ALLMESSAGES, NULL );

	if(FAILED(VOICEhr)) {
		WriteChatf("MQ2VoiceCommand::BeginVoiceOLE()::\arError\ax in registering voice command section");
		return;
	}

	//The following code checks for a navigator app and checks the state of voice commands
    VOICEhr = gpIVoiceCommand->QueryInterface(IID_IVCmdAttributes, (LPVOID FAR *)&pIVCmdAttributes);
	if (pIVCmdAttributes && SUCCEEDED(VOICEhr)) {
		DebugSpewAlways("MQ2VoiceCommand::BeginVoiceOLE()::Starting");
		pIVCmdAttributes->EnabledSet( TRUE );
  		pIVCmdAttributes->AwakeStateSet( TRUE );
		pIVCmdAttributes->Release();
	} else {
		WriteChatf("\arMQ2VoiceCommand::BeginVoiceOLE()::Navigator app and state of voice commands Failed\ax");
		return;
	}

	// Initialize command menu set variables...
	lstrcpy(VcmdName.szApplication, "Speech Reg");
	lstrcpy(VcmdName.szState, CharacterEvents);
	Language.LanguageID = LANG_ENGLISH;
	lstrcpy (Language.szDialect, "US English");

	// Create an empty command menu set...
	VOICEhr = gpIVoiceCommand->MenuCreate( &VcmdName, &Language, VCMDMC_CREATE_TEMP, &gpIVCmdMenu );
	if( FAILED(VOICEhr) ) WriteChatf("\arMQ2VoiceCommand::BeginVoiceOLE()::Failed to create a voice command set with MenuCreate()\ax");
	else UseCommands();

	if (bVoiceCommandStatus) pIVCmdAttributes->AwakeStateSet( TRUE );
	else pIVCmdAttributes->AwakeStateSet( FALSE );
}

void BeginTTSOLE() {
	// Opening TTS
	TTShr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);

	if( SUCCEEDED(TTShr) && pVoice) {
		pVoice->SetRate(iSpeakSpeed);
		pVoice->SetVolume(iSpeakVolume);
	} else {
		WriteChatf("\arMQ2VoiceCommand::TTS()::Faliure\ax");
	}
}

// Closes up OLE and frees everything else.
void EndOLE() {
	if(!bLoaded) return;
	// Free up all of OLE
	if (pVoice && SUCCEEDED(TTShr)) pVoice->Pause();
	// Voice Command
	WritePrivateProfileString(CharacterSettings,"VoiceCommandStatus",   itoa(bVoiceCommandStatus,         Buffer,10),INIFileName);
	// TTS
	WritePrivateProfileString(CharacterSettings,"SpeakTells",   itoa(bSpeakTells,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakGroup",   itoa(bSpeakGroup,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakGuild",   itoa(bSpeakGuild,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakRaid",   itoa(bSpeakRaid,         Buffer,10),INIFileName);

	WritePrivateProfileString(CharacterSettings,"SpeakTellFilter",   itoa(bSpeakTellFilter,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakGroupFilter",   itoa(bSpeakGroupFilter,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakGuildFilter",   itoa(bSpeakGuildFilter,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakRaidFilter",   itoa(bSpeakRaidFilter,         Buffer,10),INIFileName);

	WritePrivateProfileString(CharacterSettings,"SpeakVolume",   itoa(iSpeakVolume,         Buffer,10),INIFileName);
	WritePrivateProfileString(CharacterSettings,"SpeakSpeed",   itoa(iSpeakSpeed,         Buffer,10),INIFileName);

	WritePrivateProfileString(CharacterSettings,"matchTellTriggers",   matchTellTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"ignoreTellTriggers",   ignoreTellTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"matchGroupTriggers",   matchGroupTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"ignoreGroupTriggers",   ignoreGroupTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"matchGuildTriggers",   matchGuildTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"ignoreGuildTriggers",   ignoreGuildTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"matchRaidTriggers",   matchRaidTriggers,INIFileName);
	WritePrivateProfileString(CharacterSettings,"ignoreRaidTriggers",   ignoreRaidTriggers,INIFileName);

	WritePrivateProfileString(CharacterSettings,"ignoreChars",   ignoreChars,INIFileName);

	bLoaded = false;
	VoiceCommands.clear();
	CoUninitialize();
}

/**************************************************************************
 *  Voice Command notification objects
 **************************************************************************/

CIVCmdNotifySink::CIVCmdNotifySink (void) {
    m_dwRefCnt = 0;
}
CIVCmdNotifySink::~CIVCmdNotifySink (void) {
}
STDMETHODIMP CIVCmdNotifySink::QueryInterface (REFIID riid, LPVOID *ppv) {
	*ppv = NULL;

    /* always return our IUnkown for IID_IUnknown */
    if (IsEqualIID (riid, IID_IUnknown) || IsEqualIID(riid,IID_IVCmdNotifySink)) {
        *ppv = (LPVOID) this;
        return NOERROR;
    }

    // otherwise, cant find
    return ResultFromScode (E_NOINTERFACE);
}
STDMETHODIMP_ (ULONG) CIVCmdNotifySink::AddRef (void) {
    // normally this increases a reference count, but this object
    // is going to be freed as soon as the app is freed, so it doesn't
    // matter
    return 1;
}
STDMETHODIMP_(ULONG) CIVCmdNotifySink::Release (void) {
	// normally this releases a reference count, but this object
    // is going to be freed when the application is freed so it doesnt
    // matter
    return 1;
}
STDMETHODIMP CIVCmdNotifySink::CommandRecognize(DWORD dwID, PVCMDNAME pName, DWORD dwFlags, DWORD dwActionSize, PVOID pAction, DWORD dwNumLists, PSTR pszListValues, PSTR pszCommand) {
	if(!bVoiceCommandStatus) return NOERROR;
	DebugSpewAlways("MQ2VoiceCommand::CommandRecognize(%s)",pszCommand);
	if(pszCommand) {
		 for(unsigned long i=0;i<VoiceCommands.size();i++) {
			if(!stricmp(pszCommand,VoiceCommands[i].SpokenCommand)) {
				WriteChatf("MQ2VoiceCommand::CommandRecognize(\ag%s\ax) Executing:(\ag%s\ax)", pszCommand, VoiceCommands[i].Command);
				Execute(VoiceCommands[i].Command);
			}
		 }
	}	
	return NOERROR;
}
STDMETHODIMP CIVCmdNotifySink::CommandOther(PVCMDNAME pName, PTSTR pszCommand) {
	if(!bVoiceCommandStatus) return NOERROR;
	DebugSpewAlways("MQ2VoiceCommand::CommandOther(%s)",pszCommand);
	if(pszCommand) {
		for(unsigned long i=0;i<VoiceCommands.size();i++) {
			if(!stricmp(pszCommand,VoiceCommands[i].SpokenCommand)) {
				WriteChatf("MQ2VoiceCommand::CommandRecognize(\ag%s\ax) Executing:(\ag%s\ax)", pszCommand, VoiceCommands[i].Command);
				Execute(VoiceCommands[i].Command);
			}
		}
	}	
	return NOERROR;
}
// Handle notifiaction sent when recognition begins.
STDMETHODIMP CIVCmdNotifySink::CommandStart(void) {
	DebugSpewAlways("IVCmdNotifySink::CommandStart");
	return NOERROR;
}
// Handle notification sent when the menu is activated/deactivated.
STDMETHODIMP CIVCmdNotifySink::MenuActivate(PVCMDNAME pName, BOOL bActivate) {
	return NOERROR;
}
// Handle notification sent when the attribute is changed.
STDMETHODIMP CIVCmdNotifySink::AttribChanged(DWORD dwAttribute) {
	return NOERROR;
}
// Handle notification sent when the recognizer detects the beginning of an utterance.
STDMETHODIMP CIVCmdNotifySink::UtteranceBegin(void) {
	DebugSpewAlways("IVCmdNotifySink::UtteranceBegin");
	return NOERROR;
}
// Handle notification sent when the recognizer detects the end of an utterance.
STDMETHODIMP CIVCmdNotifySink::UtteranceEnd() {
	// if we're not in wide mode, indicate the status to the user
	// by changing the window title...
	DebugSpewAlways("IVCmdNotifySink::UtteranceEnd");
	return NOERROR;
}
// Handle notification sent to indicate amplitude of incoming audio data.
STDMETHODIMP CIVCmdNotifySink::VUMeter(WORD wLevel) {
	// paint the view meter abse3d on data from notification...
	return NOERROR;
}
// Handle notification sent when the recognizer detects interference
STDMETHODIMP CIVCmdNotifySink::Interference(DWORD dwType) {
//	DebugSpewAlways("IVCmdNotifySink::Interference");
	return NOERROR;
}

HRESULT UseCommands() {
	DebugSpewAlways("MQ2VoiceCommand::UseCommands()");
    SDATA   data;
    DWORD   dwNum, dwStart;
    if (VOICEhr) return VOICEhr;
    VOICEhr = gpIVCmdMenu->Deactivate(); // deactivate the menu
    // Retrieve the number of commands in the menu.
    VOICEhr = gpIVCmdMenu->Num(&dwNum);
    if (VOICEhr) return VOICEhr;
    // Remove the existing commands from the menu.
    if (dwNum) VOICEhr = gpIVCmdMenu->Remove (1, dwNum, VCMD_BY_POSITION);
    if (VOICEhr) return VOICEhr;
    // Call GetCommands, an application-defined function that fills an SDATA
    // structure with information about the commands to add to the menu.
    if (!GetCommands(&data, &dwNum)) return ResultFromScode (E_OUTOFMEMORY);
    // Add the commands to the menu.
    VOICEhr = gpIVCmdMenu->Add (dwNum, data, &dwStart);
    if (VOICEhr) return VOICEhr;
    // Free the command data.
    free (data.pData);
    // Reactivate the menu.
    VOICEhr = gpIVCmdMenu->Activate(hWnd, 0);
    return VOICEhr;
}

BOOL GetCommands(PSDATA pData, DWORD *pdwNumCommands) {
    PSTR pTemp;
    DWORD dwTotal, dwSize, dwSizeDesc, dwSizeCat;
    DWORD dwSizeCmd;
    PVCMDCOMMAND pCmd, pCmdNew;
	char    pszBegin[MAX_STRING];
    DWORD   dwCmdSize;
    DWORD   dwCmds = 0;  // Current count
    DWORD   dwCount = 1; // Command number

    dwTotal = dwSize = 0;

    pTemp = (PSTR)malloc(0);
    if (!pTemp)	return false;

    pCmd = (PVCMDCOMMAND)pTemp;
	for(unsigned long i=0;i<VoiceCommands.size();i++) {
		strcpy(pszBegin, VoiceCommands[i].SpokenCommand);
		dwCmdSize = strlen(pszBegin);
		dwSize = sizeof(VCMDCOMMAND);	// size of header
        dwSizeCmd = (dwCmdSize + 1);	// get command length

        // doubleword align
        dwSizeCmd += 3;
        dwSizeCmd &= (~3);
        dwSize += dwSizeCmd;

        dwSizeDesc = (dwCmdSize + 1);	// get description length

        // doubleword align
        dwSizeDesc += 3;
        dwSizeDesc &= (~3);
        dwSize += dwSizeDesc;

        dwSizeCat = lstrlen(CharacterEvents) + 1; // get category length

        // doubleword align
        dwSizeCat += 3;
        dwSizeCat &= (~3);
        dwSize += dwSizeCat;

        dwSize += sizeof(DWORD);	// action indicator

        dwTotal += dwSize;	// accumulate total size

        pTemp = (PSTR)realloc((PVOID)pCmd, dwTotal);	// reallocate enough memory to hold this command

        // fill in the new command
        pCmd = (PVCMDCOMMAND)pTemp;
        pTemp += (dwTotal-dwSize);
        pCmdNew = (PVCMDCOMMAND)pTemp;
        memset (pCmdNew, 0, dwSize);

        pCmdNew->dwSize = dwSize;
        pCmdNew->dwFlags = 0;
        pCmdNew->dwAction = (DWORD)(pCmdNew->abData-(PBYTE)pTemp);
        pCmdNew->dwActionSize = sizeof(DWORD);
        pCmdNew->dwCommandText = NULL;

        pTemp += (pCmdNew->abData-(PBYTE)pTemp);	// point past header to begin of data

        // action index
        *(DWORD *)pTemp = dwCount++;
        pTemp += sizeof(DWORD);

        // command
        pCmdNew->dwCommand = (DWORD)((PBYTE)pTemp - (PBYTE)pCmdNew);
        strncpy(pTemp, pszBegin, dwCmdSize);
        pTemp += dwSizeCmd;

        // description
        pCmdNew->dwDescription = (DWORD)((PBYTE)pTemp - (PBYTE)pCmdNew);
        strncpy(pTemp, pszBegin, dwCmdSize);
        pTemp += dwSizeDesc;

        // category
        pCmdNew->dwCategory = (DWORD)((PBYTE)pTemp - (PBYTE)pCmdNew);
        strcpy(pTemp, CharacterEvents);

        dwCmds++;	// we just added another command
    }
    pData->pData = (PVOID)pCmd;
    pData->dwSize = dwTotal;
    *pdwNumCommands = dwCmds;
    return true;
}

/*
// Read Costom events from ini file
*/
void LoadCostumEvents() {
	if(VoiceCommands.size()) return;
	DebugSpewAlways("[MQ2VoiceCommand::LoadCostumEvents");
	
	Commands temp; //declare a temp Commands structure
	char * p;
	
	int i,j;

	if ( DWORD cSize=GetPrivateProfileString(CharacterEvents,NULL,NULL,Buffer,MAX_STRING,INIFileName) ) {
		p = Buffer; //declare a pointer

		char szTemp[MAX_STRING] = {0};

		j = i = 0;
		//Here we go; The idea here is to loop while there are characters to get (defined by i)

		while(i<(int)cSize) {
			//While p is pointing to a non-null character
			if(*p) {
				j=0;
				char szbuff[MAX_STRING] ={0};
				while(*p) {
						//start filling the ith dimension
					szbuff[j] = *p;
						//We've grabbed a character, move the poitner to the next position
					p++; //increment p Pointer
					j++; //increment J counter
					i++; //increment i counter
				}
				GetPrivateProfileString(CharacterEvents,szbuff,"",szTemp,MAX_STRING,INIFileName);
				strcpy (temp.SpokenCommand, szbuff);
				strcpy (temp.Command, szTemp);
				VoiceCommands.push_back(temp);
				DebugSpewAlways("MQ2VoiceCommand::Added(SpokenCommand:%s Command:%s From:%s)",temp.SpokenCommand,temp.Command,CharacterEvents);
			} else {
				i++;
				if (i<(int)cSize) p++;
			}
		}
	}
	if ( DWORD sSize=GetPrivateProfileString("GlobalCommands",NULL,NULL,Buffer,MAX_STRING,INIFileName) ) {
		p = Buffer; //declare a pointer

		char szTemp[MAX_STRING] = {0};

		j = i = 0;
		//Here we go; The idea here is to loop while there are characters to get (defined by i)

		while(i<(int)sSize) {
			//While p is pointing to a non-null character
			if(*p) {
				j=0;
				char szbuff[MAX_STRING] ={0};
				while(*p) {
						//start filling the ith dimension
					szbuff[j] = *p;
						//We've grabbed a character, move the poitner to the next position
					p++; //increment p Pointer
					j++; //increment J counter
					i++; //increment i counter
				}
				GetPrivateProfileString("GlobalCommands",szbuff,"",szTemp,MAX_STRING,INIFileName);
				strcpy (temp.SpokenCommand, szbuff);
				strcpy (temp.Command, szTemp);
				VoiceCommands.push_back(temp);
				DebugSpewAlways("MQ2VoiceCommand::Added(SpokenCommand:%s Command:%s From:Settings)",temp.SpokenCommand,temp.Command);
			} else {
				i++;
				if (i<(int)sSize) p++;
			}
		}
	}
}

BOOL InGames() {
	if(!MQ2Globals::gbInZone)                    return false;
	if(MQ2Globals::gGameState!=GAMESTATE_INGAME) return false;
	if(!GetCharInfo2())                          return false;
	if(!GetCharInfo())                           return false;
	if(!GetCharInfo()->pSpawn)                   return false;
	return true;
}

/*
//	Ingame commands:
//	/voice [on|off] <- Not Created yet...
*/
VOID VoiceCommand(PSPAWNINFO pChar, PCHAR szLine) {	
	DebugSpewAlways("MQ2VoiceCommand::VoiceCommand()");
	if(!InGames()) return;	

	long iParm=0;
	do {
		GetArg(Buffer,szLine,++iParm);
		if(Buffer[0]==0) return;
		//The following code checks for a navigator app and 
		//checks the state of voice commands
		if(!strnicmp(Buffer,"on",2)) {
			if (pIVCmdAttributes && SUCCEEDED(VOICEhr)) {
				bVoiceCommandStatus = true;
				pIVCmdAttributes->AwakeStateSet( TRUE );
				WriteChatf("MQ2VoiceCommand ON");
			}
		} else if(!strnicmp(Buffer,"off",3)) {
			if (pIVCmdAttributes && SUCCEEDED(VOICEhr)) {
				bVoiceCommandStatus = false;
				pIVCmdAttributes->AwakeStateSet( FALSE );
			}
			WriteChatf("MQ2VoiceCommand OFF");
		} else if(!strnicmp(Buffer,"speed",7)) {
			GetArg(Buffer,szLine,++iParm);
			iSpeakSpeed = atoi(Buffer);
			if(iSpeakSpeed<-10) iSpeakSpeed = -10;
			else if(iSpeakSpeed>10) iSpeakSpeed = 10;
			if( SUCCEEDED(TTShr) && pVoice) pVoice->SetRate(iSpeakSpeed);
		} else if(!strnicmp(Buffer,"volume",7)) {
			GetArg(Buffer,szLine,++iParm);
			iSpeakVolume = atoi(Buffer);
			if(iSpeakVolume<=0) iSpeakVolume = 0;
			else if(iSpeakVolume>100) iSpeakVolume = 100;
			if( SUCCEEDED(TTShr) && pVoice) pVoice->SetVolume(iSpeakVolume);
		} else if(!strnicmp(Buffer,"tts",3)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) {
				bSpeakTells = bSpeakGroup = bSpeakGuild = bSpeakRaid = true;
				WriteChatf("MQ2VoiceCommand TTS ALL ON");
			} else if(!strnicmp(Buffer,"off",3)) {
				bSpeakTells = bSpeakGroup = bSpeakGuild = bSpeakRaid = false;
				WriteChatf("MQ2VoiceCommand TTS ALL OFF");
			}
		} else if(!strnicmp(Buffer,"tell",4)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) bSpeakTells = true;
			else if(!strnicmp(Buffer,"off",3)) bSpeakTells = false;
		} else if(!strnicmp(Buffer,"group",5)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) bSpeakGroup = true;
			else if(!strnicmp(Buffer,"off",3)) bSpeakGroup = false;
		} else if(!strnicmp(Buffer,"guild",5)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) bSpeakGuild = true;
			else if(!strnicmp(Buffer,"off",3)) bSpeakGuild = false;
		} else if(!strnicmp(Buffer,"raid",4)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) bSpeakRaid = true;
			else if(!strnicmp(Buffer,"off",3)) bSpeakRaid = false;
		} else if(!strnicmp(Buffer,"filter",6)) {
			GetArg(Buffer,szLine,++iParm);
			if(!strnicmp(Buffer,"on",2)) {
				bSpeakTellFilter = bSpeakGroupFilter = bSpeakGuildFilter = bSpeakRaidFilter = true;
			} else if(!strnicmp(Buffer,"off",3)) {
				bSpeakTellFilter = bSpeakGroupFilter = bSpeakGuildFilter = bSpeakRaidFilter = false;
			} else if( !strnicmp(Buffer,"tell",4) ) {
				GetArg(Buffer,szLine,++iParm);
				bSpeakTellFilter=!strnicmp(Buffer,"on",2)?true:false;
			} else if( !strnicmp(Buffer,"group",5) ) {
				GetArg(Buffer,szLine,++iParm);
				bSpeakGroupFilter=!strnicmp(Buffer,"on",2)?true:false;
			} else if( !strnicmp(Buffer,"guild",5) ) {
				GetArg(Buffer,szLine,++iParm);
				bSpeakGuildFilter=!strnicmp(Buffer,"on",2)?true:false;
			} else if( !strnicmp(Buffer,"raid",4) ) {
				GetArg(Buffer,szLine,++iParm);
				bSpeakRaidFilter=!strnicmp(Buffer,"on",2)?true:false;
			}
			WriteChatf("TTS Filter:: Tell (%s) Group (%s) Guild (%s) Raid (%s)",bSpeakTellFilter?"\agon\ax":"\aroff\ax",bSpeakGroupFilter?"\agon\ax":"\aroff\ax",bSpeakGuildFilter?"\agon\ax":"\aroff\ax",bSpeakRaidFilter?"\agon\ax":"\aroff\ax");
		} else if(!strnicmp(Buffer,"status",5)) {
			WriteChatf("*****************************************");
			WriteChatf("MQ2VoiceCommand Status Voice(%s) TTS(%s)",(bVoiceCommandStatus && SUCCEEDED(VOICEhr) && gpIVoiceCommand)?"\agon\ax":"\aroff\ax",(SUCCEEDED( TTShr ) && pVoice)?"\agon\ax":"\aroff\ax");

			WriteChatf("TTS Tell (%s) Group (%s) Guild (%s) Raid (%s)",bSpeakTells?"\agon\ax":"\aroff\ax",bSpeakGroup?"\agon\ax":"\aroff\ax",bSpeakGuild?"\agon\ax":"\aroff\ax",bSpeakRaid?"\agon\ax":"\aroff\ax");
			WriteChatf("TTS Filter:: Tell (%s) Group (%s) Guild (%s) Raid (%s)",bSpeakTellFilter?"\agon\ax":"\aroff\ax",bSpeakGroupFilter?"\agon\ax":"\aroff\ax",bSpeakGuildFilter?"\agon\ax":"\aroff\ax",bSpeakRaidFilter?"\agon\ax":"\aroff\ax");
			WriteChatf("TTS Settings:: Ignore Chars (\ar%s\ax) Speak Volume (\ag%d\ax) Speak Speed (\ag%d\ax)",ignoreChars,iSpeakVolume,iSpeakSpeed);

			WriteChatf("Filters:");
			WriteChatf("	TELL match (\ag%s\ax) ignore (\ar%s\ax)",matchTellTriggers,ignoreTellTriggers);
			WriteChatf("	GROUP match (\ag%s\ax) ignore (\ar%s\ax)",matchGroupTriggers,ignoreGroupTriggers);
			WriteChatf("	GUILD match (\ag%s\ax) ignore (\ar%s\ax)",matchGuildTriggers,ignoreGuildTriggers);
			WriteChatf("	RAID match (\ag%s\ax) ignore (\ar%s\ax)",matchRaidTriggers,ignoreRaidTriggers);
			WriteChatf("*****************************************");
			break;
		} else {
			WriteChatf("*****************************************");
			WriteChatf("MQ2VoiceCommand::Help");
			WriteChatf("/voice [on|off] to pause/unpause");
			WriteChatf("/voice volume 0 - 100 TTS volume");
			WriteChatf("/voice speed -10 - 10 TTS speed");
			WriteChatf("/voice tts [on|off] to tell ALL TTS on/off");
			WriteChatf("/voice tell [on|off] to tell TTS on/off");
			WriteChatf("/voice group [on|off] to group TTS on/off");
			WriteChatf("/voice guild [on|off] to guild TTS on/off");
			WriteChatf("/voice raid [on|off] to raid TTS on/off");
			WriteChatf("/voice filter [on|off] to filter ALL TTS on/off");
			WriteChatf("/voice filter tell [on|off] to filter tell TTS on/off");
			WriteChatf("/voice filter group [on|off] to filter group TTS on/off");
			WriteChatf("/voice filter guild [on|off] to filter guild TTS on/off");
			WriteChatf("/voice filter raid [on|off] to filter raid TTS on/off");
			WriteChatf("*****************************************");
			break;
		}
	}
	while(true);
}

bool CheckChar(char szLine) {
	char * p = ignoreChars;	
	while(true) {
		//While p is pointing to a non-null character
		if(*p) {
			if(  szLine == *p ) return false;
			p++;
		} else {
			break;
		}
	}
	return true;
}

PLUGIN_API VOID OnTextToSpeech(PCHAR szLine) {
	if(!pVoice || FAILED( TTShr )) return;
	char szTemp[MAX_STRING];
	WCHAR wide[MAX_STRING];
	unsigned int i,j;
	bool Begin=true;
 
	for (i=j=0; i < strlen(szLine); i++) {
		if(szLine[i]==0x12) {
			if (Begin) {
				Begin=false;
				i+=50;
			} else {
				Begin=true;
			}
		} else {
			if(szLine[i]==0x60) szTemp[j++]=0x27;
			else if ( CheckChar(szLine[i]) ) szTemp[j++]=szLine[i];
		}
	}
	szTemp[j]=0x00;
 
	DebugSpewAlways("MQ2VoiceCommand::OnTextToSpeech(%s)",szTemp);

	for (i=0; i < MAX_STRING; wide[i++]=0) {
		mbstowcs(wide, szTemp, strlen(szLine));
	}
 
	if( SUCCEEDED( TTShr ) && pVoice) {
		TTShr = pVoice->Speak(wide, SPF_ASYNC, NULL );		
	} else {
		WriteChatf("\ar[MQ2VoiceCommand::OnTextToSpeech()]::Error\ax");
	}
}

VOID SpeakNow(PSPAWNINFO pChar,PCHAR szLine) {
	OnTextToSpeech(szLine);
}


// Called after entering a new zone
PLUGIN_API VOID OnBeginZone(VOID)
{
	DebugSpewAlways("MQ2VoiceCommand::OnBeginZone()");
	/*
	if( SUCCEEDED( TTShr ) && pVoice) {
		pVoice->Pause();		
	}
	*/
	bVoiceCommandStatusZone = bVoiceCommandStatus;

	if (bVoiceCommandStatusZone && pIVCmdAttributes && SUCCEEDED(VOICEhr)) {
		bVoiceCommandStatus = false;
		pIVCmdAttributes->AwakeStateSet( FALSE );
	}
}

// Called after entering a new zone
PLUGIN_API VOID OnEndZone(VOID)
{
	DebugSpewAlways("MQ2VoiceCommand::OnEndZone()");
	/*
	if( SUCCEEDED( TTShr ) && pVoice) {
		pVoice->Resume();
	}
	*/
	if (bVoiceCommandStatusZone && pIVCmdAttributes && SUCCEEDED(VOICEhr)) {
		bVoiceCommandStatus = true;
		pIVCmdAttributes->AwakeStateSet( TRUE );
	}
}

// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID) {
	DebugSpewAlways("Initializing MQ2VoiceCommand");
	AddCommand("/voice",VoiceCommand);
	AddCommand("/tts",SpeakNow);
	//BeginOLE();
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID) {
	DebugSpewAlways("Shutting down MQ2VoiceCommand");
	RemoveCommand("/voice");
	RemoveCommand("/tts");
	EndOLE();
}

PLUGIN_API VOID SetGameState(DWORD GameState) {
	DebugSpewAlways("MQ2VoiceCommand::VoiceCommandSetGameState()");
	if ( gGameState==GAMESTATE_CHARSELECT) EndOLE();
}

bool FounedPlayer(PCHAR Player, PCHAR Filter) {
	long iParm=0;
	char zRule[MAX_STRING];
	do {
		GetArg(zRule,Filter,++iParm,FALSE,FALSE,FALSE,'|');
		if(zRule[0]==0) break;
		if( !stricmp(Player,zRule) ) return true;
	}
	while(true);
	return false;
}

PLUGIN_API DWORD OnIncomingChat(PCHAR Line, DWORD Color) {
	if ( (bSpeakTells && Color==USERCOLOR_TELL) || (bSpeakGroup && Color==USERCOLOR_GROUP) || (bSpeakGuild && Color==USERCOLOR_GUILD) || (bSpeakRaid && Color==USERCOLOR_RAID) ) {
		char SenderName[MAX_STRING];
		GetArg(SenderName,Line,1);

		if (Color==USERCOLOR_TELL) {
			if(bSpeakTellFilter) {
				if( FounedPlayer(SenderName,matchTellTriggers) ) OnTextToSpeech(Line);
			} else {
				if( !FounedPlayer(SenderName,ignoreTellTriggers) ) OnTextToSpeech(Line);
			}
		} else if(Color==USERCOLOR_GROUP) {
			if(bSpeakGroupFilter) {
				if( FounedPlayer(SenderName,matchGroupTriggers) ) OnTextToSpeech(Line);
			} else {
				if( !FounedPlayer(SenderName,ignoreGroupTriggers) ) OnTextToSpeech(Line);
			}
		} else if(Color==USERCOLOR_GUILD) {
			if(bSpeakGuildFilter) {
				if( FounedPlayer(SenderName,matchGuildTriggers) ) OnTextToSpeech(Line);
			} else {
				if( !FounedPlayer(SenderName,ignoreGuildTriggers) ) OnTextToSpeech(Line);
			}
		} else if(Color==USERCOLOR_RAID) {
			if(bSpeakRaidFilter) {
				if( FounedPlayer(SenderName,matchRaidTriggers) ) OnTextToSpeech(Line);
			} else {
				if( !FounedPlayer(SenderName,ignoreRaidTriggers) ) OnTextToSpeech(Line);
			}
		}
	}
	return 0;
}

PLUGIN_API VOID OnPulse(VOID)
{
	BeginOLE();
}